package org.fjsei.yewu.maintenance;

import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.DateRangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;       //注意同名Query
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import com.alibaba.fastjson2.JSON;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import lombok.extern.slf4j.Slf4j;
import md.cm.base.*;
import md.cm.geography.*;
import md.cm.unit.*;
import md.specialEqp.*;
import md.specialEqp.inspect.*;
import md.specialEqp.type.*;
import md.system.User;
//import org.elasticsearch.index.query.ExistsQueryBuilder;
//import org.elasticsearch.index.query.TermsQueryBuilder;
import org.fjsei.yewu.aop.MetricsLogger;
import org.fjsei.yewu.aop.hibernate.MyMassIndexingLoggingMonitor;
import org.fjsei.yewu.entity.fjtj.*;
import org.fjsei.yewu.entity.incp.*;
import org.fjsei.yewu.exception.CommonGraphQLException;
import org.fjsei.yewu.exception.UserAccessDenyException;
import org.fjsei.yewu.index.*;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.jpa.QBeanMy;
import org.fjsei.yewu.pojo.SliceSyncRes;
import org.fjsei.yewu.repository.StoreSync;
import org.fjsei.yewu.repository.StoreSyncRepository;
import org.fjsei.yewu.repository.maint.JsliceMang;
import org.fjsei.yewu.repository.maint.JsliceMangRepository;
import org.fjsei.yewu.security.JwtUserDetailsService;
import org.fjsei.yewu.service.SearchService;
import org.fjsei.yewu.service.third.DataRespond;
import org.fjsei.yewu.service.third.InfRecvConvert;
import org.fjsei.yewu.util.Tool;
import org.jgroups.util.Triple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.domain.*;
//import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

import java.time.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;
import static java.lang.Math.abs;
//import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.fjsei.yewu.util.Tool.longstr2Date;


//后台维护!

@Slf4j
@Controller
public class MaintenanceMutation extends InfRecvConvert {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private GenericApplicationContext context;
    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;
    @Autowired
    private Equipments eQPRepository;
   // @Autowired    private EqpEsRepository eqpEsRepository;
    @Autowired   private IspRepository iSPRepository;
    //@Autowired    private Units units;
    @Autowired   private CompanyEsRepository companyEsRepository;
    @Autowired   private PersonEsRepository personEsRepository;
    @Autowired   private JcUntMgeRepository jcUntMgeRepository;

    @PersistenceContext(unitName = "entityManagerFactoryIncp")
    private EntityManager emIncp;

//    @Autowired
//    private final JwtTokenUtil jwtTokenUtil=new JwtTokenUtil();
    @Autowired private UntMgeRepository untMgeRepository;

    @Autowired private ProvinceRepository provinceRepository;
    @Autowired private CountryRepository countryRepository;
    @Autowired private CityRepository cityRepository;
    @Autowired private CountyRepository countyRepository;
    @Autowired private TownRepository  townRepository;
    @Autowired private HouseMgeRepository houseMgeRepository;
    @Autowired private UntDeptRepository untDeptRepository;
    @Autowired private UntSecudeptRepository untSecudeptRepository;
    @Autowired private DivisionRepository divisionRepository;
    @Autowired private OfficeRepository officeRepository;
    @Autowired private JsliceMangRepository jsliceMangRepository;
    @Autowired private JcdunitRepository jcdunitRepository;
    @Autowired private JispUnitRepository jispUnitRepository;
    @Autowired private JuntSecudeptRepository juntSecudeptRepository;
    @Autowired private JcuntDeptRepository jcuntDeptRepository;
    @Autowired private JcPermtUntRepository jcPermtUntRepository;
    @Autowired private PipingUnitRepository  pipingUnitRepository;
    @Autowired private HrDeptinfoRepository  hrDeptinfoRepository;
    @Autowired  private TaskRepository taskRepository;

    @Autowired   private Persons persons;
    private final DetailRepository  details;
    private final Companies companies;

//    private volatile ReadComm buffAKread=null;  //缓存刚刚独到的大数据集:AK作业，分片作业可以重复利用，offs复位时手动释放。 不用static；synchronized
    @Autowired  private SearchService searchService;

    public MaintenanceMutation(BatchLoaderRegistry registry, Companies companies, DetailRepository details) {
        this.companies = companies;
        this.details = details;
    }

    public Boolean setupFromLegacy_JU(String arg) {
        if("Pz".equals(arg))    return setupFromLegacy_Pz部门生成(arg);
        else if("Ls".equals(arg))    return setupFromLegacy_Ls临时Isp消除(arg);
        else if("DP".equals(arg))    return setupFromLegacy_DP检验HR部门(arg);
        else return false;
    }
    /**单一条 Unit的恢复：把旧检验单位表导入Unit
     * */
    public <T> Triple  constructUnit_Su检验批Jy(T inObj) {
        String retRes="";
        CompanyEs companyEs=null;
        PersonEs personEs=null;
        UntMge untMge= (UntMge)inObj;    //？？泛型==欺骗编译器;
        //log.info("分片导入检验Unit:UntMge {}", untMge.getUNT_ID()); 目标分片的依照表=UntMge，全部的记录，缺省是没顺序:有问题，新插入的怎么办？。
        if(!StringUtils.hasText(untMge.getUNT_NAME()) )    return new Triple("nameNull", null, null);
        //if(2==untMge.getUNT_STATE())   return new Triple("注销态", null, null);
        if(!StringUtils.hasText(untMge.getUNT_ORG_COD()) )  return new Triple("no空", null, null);
        //旧平台区分不明确！[判定可能是]是个人吗? 没有很好自动判定个人企业办法，但求原则是一致。多次同步保证一样的分辨！
        Unit unit;
        Adminunit adminunit=adminunitRepository.findTopByAreacode(untMge.getUNT_AREA_COD());
        boolean isPerson= "Z01".equals(untMge.getINDUSTRY_PROP_COD()) || untMge.getUNT_NAME().length()<=3 ||
                   (untMge.getUNT_ORG_COD().length()==18 && untMge.getUNT_ORG_COD().startsWith("35"));
        //因为@Column(length = 40) 提交阶段才会保存的？
        if(untMge.getUNT_NAME().length()>20)    //超过40导致无法执行成功。事务也会重试最后超时的"Transaction silently rolled back because it has been marked as rollback-only",
            isPerson=false;
        if(!isPerson)
        {
            if(untMge.getUNT_NAME().length()>150)
                return new Triple("名字太长", null, null);
            Company company= companies.findByName(untMge.getUNT_NAME());
            if(null!=company){
                unit= units.findUnitByCompany(company);
                if(null==unit)  return new Triple("unitNull", null, null);
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同名", null, null);
                }
            }else{
                company=Company.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            //多数属性可再次覆盖：但关键字段 name no 还是不要变更了。
            company= company.toBuilder().address(untMge.getUNT_ADDR()).linkMen(untMge.getUNT_LKMEN())
                    .phone(untMge.getUNT_MOBILE()).tel(untMge.getUNT_PHONE()).ad(adminunit)
                    .lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().company(company).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                companies.save(company);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
//            companyEs=new CompanyEs();        已被HS淘汰
//            BeanUtils.copyProperties(company,companyEs);
        }
        else{       //个人 身份证 是唯一性定位码
            Person person= persons.findByNo(untMge.getUNT_ORG_COD());
            if(null!=person){
                unit= units.findUnitByPerson(person);
                if(null==unit)  new Triple("unitNull", null, null);
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同码", null, null);
                }
            }else{
                person=Person.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            //个人只有手机，没有联系人。adminunit当前流动居住地；lat+lon; 关键字段 name no不能再次变更。
            person= person.toBuilder().address(untMge.getUNT_ADDR())
                    .phone((null==untMge.getUNT_MOBILE() || untMge.getUNT_MOBILE().length()<8)? untMge.getUNT_PHONE() : untMge.getUNT_MOBILE())
                    .ad(adminunit).lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().person(person).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                persons.save(person);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
//            personEs=new PersonEs();       已被HS淘汰
//            BeanUtils.copyProperties(person,personEs);
        }
        untMge.setUunit(unit.getId());   //事务当中查询出来的数据库实体：是可以自动保存的,new()不算。
        return new Triple(retRes, companyEs, personEs);   //2个需做批量保存的仓库实体
    }
    /**监察的单位优先覆盖检验的：    针对性添加索引，提速7倍了；绝大多数数据库负载是在非主键的查询过滤上的。
     * 返回非空的三元组<结果; 公司ES; 个人ES>, 目的是ES做批量保存;
     * */
    public <T> Triple constructUnit_Su监察批cM(T inObj) {
        String retRes="";
        CompanyEs companyEs=null;
        PersonEs personEs=null;
        JcUntMge untMge= (JcUntMge)inObj;
        if(!StringUtils.hasText(untMge.getUNT_NAME()) )    return new Triple("nameNull", null, null);
        //if(2==untMge.getUNT_STATE())   return new Triple("注销态", null, null);
        if(!StringUtils.hasText(untMge.getUNT_ORG_COD()) )  return new Triple("no空", null, null);
        Unit unit;
        Adminunit adminunit=adminunitRepository.findTopByAreacode(untMge.getUNT_AREA_COD());
        boolean isPerson= "Z01".equals(untMge.getINDUSTRY_PROP_COD()) || untMge.getUNT_NAME().length()<=3 ||
                (untMge.getUNT_ORG_COD().length()==18 && untMge.getUNT_ORG_COD().startsWith("35"));
        if(!isPerson)
        {
            QCompany qm = QCompany.company;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.name.eq(untMge.getUNT_NAME()));
            Company company= companies.findOne(builder).orElse(null);
            if(null!=company){
                unit= units.findUnitByCompany(company);
                if(null==unit)  return new Triple("unitNull", null, null);    //不允许unit直接丢弃关联的Company或Person
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同名", null, null);
                }
            }else{
                company=Company.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            company= company.toBuilder().address(untMge.getUNT_ADDR()).linkMen(untMge.getUNT_LKMEN())
                    .phone(untMge.getUNT_MOBILE()).tel(untMge.getUNT_PHONE()).ad(adminunit)
                    .lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().company(company).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                companies.save(company);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
            companyEs=new CompanyEs();
            BeanUtils.copyProperties(company,companyEs);
        }
        else{
            QPerson qm = QPerson.person;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.no.eq(untMge.getUNT_ORG_COD()));
            Person person= persons.findOne(builder).orElse(null);
            if(null!=person){
                unit= units.findUnitByPerson(person);
                if(null==unit)  return new Triple("unitNull", null, null);
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同码", null, null);
                }
            }else{
                person=Person.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            person= person.toBuilder().address(untMge.getUNT_ADDR())
                    .phone((null==untMge.getUNT_MOBILE() || untMge.getUNT_MOBILE().length()<8)? untMge.getUNT_PHONE() : untMge.getUNT_MOBILE())
                    .ad(adminunit).lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().person(person).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                persons.save(person);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
            personEs=new PersonEs();
            BeanUtils.copyProperties(person,personEs);      //返回存入Triple批量保存
        }
        untMge.setUunit(unit.getId());       //事务当中查询出来的数据库实体：是可以自动保存的,new()不算。
        return new Triple(retRes, companyEs, personEs);   //2个需做批量保存的仓库实体
    }
    //改成批量存ES；可以节省掉88.7%时间，提速8倍！惊人的批处理性能。
    //@Transactional(propagation = REQUIRES_NEW) 对于CRDB没有用，不能嵌套事务，只需要注解最外层函数一次。
    public JsliceMang constructUnitSu(JsliceMang jobm,String arg) {
        Function<SliceSyncRes, Triple>  sliceJob= parb ->{
            if("cM".equals(arg))    return constructUnit_Su监察批cM(parb);
            else if("Jy".equals(arg))    return constructUnit_Su检验批Jy(parb);
            else if("Pm".equals(arg))    return constructUnit_Su监察批Pm(parb);
//            else if("Bm".equals(arg))    return constructUnit_Su检验批Jy(parb);
            else return null;
        };
        if("cM".equals(arg)){
            if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
            return sliceJobBatch(jobm,jcUntMgeRepository, sliceJob, companyEsRepository, personEsRepository);
        }else if("Jy".equals(arg)){
            //CRDB集群，emFjtj emSei 几个都是同一个数据库集群的；全局 事务还需要分开管理者吗？
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return sliceJobBatch(jobm,untMgeRepository, sliceJob, companyEsRepository, personEsRepository);
        }else if("Pm".equals(arg)){
            //内层函数加@Transactional 会报错 No EntityManager with actual transaction available for current thread - cannot reliably process 'joinTransaction'
            //外层函数加@Transactional的必须加上：才会保存incp数据库的。 没上joinTransaction报错: no transaction is in progress | 不会保存
            if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();       //没添加joinTransaction就不会保存数据库的。
            return sliceJobBatch(jobm,jcPermtUntRepository, sliceJob, null, null);
        }
//        else if("Bm".equals(arg)){
//            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
//            return sliceJobBatch(jobm,companies, sliceJob, null, null);
//        }
        else return null;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    //新的模式：分片维护作业的入口： 前端可并发点击，就算同一个浏览页面也会两次点击一个按钮导致2个线程2个事务冲突。
    /**pasw参数：authID 或者是'密码password'的形式,如果是后者分片作业就初始启动直接返回。
     * 前提：极端下确保1分钟内必须完成一个分片作业。一个客户可能连续占用，无法抢任务。
     * 缺点是必须人工网页开着才能发起后台作业。
     * syncEqpFromLegacy_AU()子函数需保存Repository不是主数据库的！必须controller入口加@Transactional才能真正保存数据。
     * CRDB数据库表现：会锁住表，查询整表或count都会被拖延的。小强数据库默认就是串行化的事务，应用层不需要处理并发冲突。和MySql逻辑不同。
     * (非分片)太大的表拆分成多次操作：arg参数进行扩展？"Jy:$idbegin1,$idend2"解析出分片:ID">起点&&<=终点"范围，非分片不用offset。
     * 这个启点不应该添加事务： 事务往内部方法移动。
     * 只能在graphQL直接对接的接口函数外层函数上加@Transactional才能确保不会- no Session报错。除非把FetchType.LAZY改成EAGER; 不能不加@Transactional来延长session周期
     * */
    @Deprecated
    public JsliceMang syncEqpFromLegacy外套模式(String type,String arg,String pasw,int offset) {
        int numCalls = 0;
        //这样允许分解多个步骤，不是同一个Transactional单一事务之内进行的。 最上面这层函数加@Transactional还是必须的。
        JsliceMang jsliceMang=null;
        do {
            numCalls++;
            try {
                //下面这句也会抛出异常然后直接返回给前端的，没有执行剩下的控制逻辑！
                jsliceMang=context.getBean(MaintenanceMutation.class).syncEqpFromLegacy(type, arg, pasw, offset);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if(null==jsliceMang){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }       //导致其它种类错误 也会重复，最后还不会抛出异常给前端！
        } while (null==jsliceMang && numCalls < 20);
        //乱catch()引起逻辑异常：这样会导致连续20个UserAccessDenyException: 没登录； 但就是返回给前端null!
        //【差别大】不能直接用 return syncFromLegacyInner(type, arg, pasw, offset);
        return jsliceMang;
    }
    //@Transactional(rollbackFor = Exception.class) 若是取消@Transactional立刻都能保存提交DB;
    /**
     * 若 @Transactional 注解在内部函数，graphQL会报错。 因为return JsliceMang对象的内省User是Lazy Fetch的，所以可能还没有User数据您就return了。
     * 这种情况：根据返回的JsliceMang以及前端内省实际需要字段，@Transactional应该添加到graphQL直接对接的接口函数外层函数上面。
     * 内层外层函数两个地方都不加@Transactional也是报错的。仅仅加在内层函数也是报错的could not initialize proxy  - no Session。 解决no Session办法：
     * 老办法open-in-view=true必须把@Transactional加到graphQL直接对接的接口函数外层函数上，否则JsliceMang没有做save直接return的就不会保存到数据库。
     * Transactional默认针对RuntimeException|Error回滚的,写写冲突时刻，无法确保offs不会在异常抛出JpaSystemException后回滚，就是offs还是会往前走。
     * Transactional(rollbackFor = Exception.class)添加应用定义异常回滚, rollbackFor默认只对RuntimeException回滚， rollbackFor加上终于可以确保JsliceMang回滚。
     * Transactional Fjtj数据库的表参与事务出现了写冲突：syncEqpFromLegacy返回提交时刻才会捕捉到异常的。JsliceMang.save()为何不能撤销掉啊？不是同一个Database问题。
     * 写写冲突：同一个数据库的，可能会被直接串行化而免去回滚异常，也有可能是异常回滚；跨两个数据库辅助数据库写写冲突可能抛异常但光光其它Database异常的主数据库可能不会回滚。
     * 一个用户连续两次点击按钮发送请求：导致多线程并发，事务针对DB，但对于java无法阻止并发逻辑错误！volatile也没法规避。
     * Transactional(rollbackFor=Exception.class, timeout=30)就能抛出JpaSystemException: transaction timeout expired;马后炮等到做完才说超时？
     * 事务timeout时间：设置太长不行，只好尽量分解任务，时间包括对外接口也会阻塞等待的时间。
    * */
    @MetricsLogger
    @MutationMapping
    @Transactional(rollbackFor=Exception.class, timeout=60)
    public JsliceMang syncEqpFromLegacy(@Argument String type,@Argument String arg,@Argument String pasw,@Argument Integer offset) {
        User user=checkAuth();
        if(null==user)  throw new UserAccessDenyException("没登录");
        Boolean doModoffs= StringUtils.hasText(arg) && arg.startsWith("ModOffs:");    //复合多个按钮的操作接口函数模式 @已经作废”修改偏移量“按钮没有用"ModOffs:"；
        JsliceMang ackjm=new JsliceMang();
        JsliceMang jobm;
        if("OD".equals(type) || "Dm".equals(type)) {
            if(doModoffs)  arg=arg.substring(8);    //ModOffs:剔除掉
            jobm = jsliceMangRepository.findByName(type + arg);     //并发多线程模式作业:需要独立管理器
        }
        else
            jobm= jsliceMangRepository.findByName(type);
        Assert.isTrue(null!=jobm,"没job:"+type+" "+arg);
        if(doModoffs) {     //@没用了这个！
            jobm.setOffs(Math.max(offset, 0));
            jobm.setFireor(user);
            jobm.setAuid(null);
            if(0==offset) {    //手动释放内存，不能立刻看见释放
//                buffAKread = null;
                System.gc();
                jobm.setSortat(null);       //适配searchAfter模式:只能从头再来。
                //jsliceMangRepository.save(jobm);      //save不清楚是否新的Insert可能性：所以要争用索引：和其它人save(其它key)冲突!
                jsliceMangRepository.upsert(jobm);      //upsert()没效果；主要原因=findByName()没索引-导致“全表搜索”-引起SSI事务失败！
            }else{
                jsliceMangRepository.upsert(jobm);
                Assert.isTrue(!StringUtils.hasText(jobm.getSortat()),"searchAfter模式:不能自动设置偏移量,只能手动改表值");
                Assert.isTrue(offset<0,"并发不能用参数offs");
            }
            return jobm;
        }
        if("OD".equals(type))
            Assert.isTrue(offset<0,"并发不能用参数offs");
        Boolean innerOk=true, alljobFin=false;
        LocalDateTime now= LocalDateTime.now();
        //if(!StringUtils.hasText(type) || !StringUtils.hasText(pasw))  return jobm;
        if(null!=jobm.getFireor() && jobm.getFireor().getId().equals(user.getId())) {
            if ("请再三确认分片偏移量已经不再继续移动".equals(jobm.getAuid()) && "sure".equals(pasw)) {
                String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
                jobm.setDesc(String.format("抢占任务终于可行了"));
                jobm.setLast(now);
                jobm.setAuid(uuid);
                jsliceMangRepository.save(jobm);
                return jobm;
            }
        }
        if(!pasw.equals(jobm.getAuid()) && !pasw.equals(jobm.getPasw()) ){
            jobm.setDesc("密码或auid不对");
            jobm.setAuid(null);
            return jobm;    //@自动保存的！
        }
        //【阻塞模式】支持一次性作业部分：(非分片的作业) ：调整graphql.ansyc默认30秒,超时丢弃不给前端应答。
        if("JU".equals(type)) {
            innerOk= setupFromLegacy_JU(arg);
            alljobFin=true;
        }
        else if("AD".equals(type)) {
            innerOk= syncUnitFromLeg_AD(type, arg);
            alljobFin=true;
        }else if("SY".equals(type)) {
            jobm.setDesc( setupFromLegacy_SY(arg) );
            alljobFin=true;
        }
        if(!alljobFin && jobm.getOffs()>=jobm.getTotl() )
            alljobFin=true;
        if(alljobFin){
            if(!StringUtils.hasText(jobm.getDesc()))      jobm.setDesc(innerOk? "全部都完成": "失败");
            jobm.setAuid(null);
            return jobm;
        }
        Duration duration;
        if(null==jobm.getLast())    duration= Duration.ofHours(1);
        else    duration= Duration.between(jobm.getLast(), now);
        if(!pasw.equals(jobm.getAuid()) && duration.toSeconds()<=60){
            //jobm.setAuid("正在抢占");   toMinutes<=1实际代表极限120秒
            jobm.setDesc(String.format("稍等%d秒:解锁中",61-duration.toSeconds()) );
            jobm.setAuid(null);
            jsliceMangRepository.save(jobm);
            return jobm;
        }
        String uuid=UUID.randomUUID().toString().trim().replaceAll("-", ""); //生成UUID重复几率非常小;
        //超过1分钟了，就解除锁定
        if(duration.toMinutes()>1){
            if(!pasw.equals(jobm.getAuid())) {          // auid != pasw;  pasw !='sure'
                if(offset>=0)  jobm.setOffs(offset);
                jobm.setDesc(String.format("超时自动解锁,%d秒", duration.toSeconds()));
                //jobm.setAuid("确认停止了");
                jobm.setFireor(user);
                //抢占任务特别注意！ 很可能前面的分片作业任务还没有真正完成啊[有重复可能]。 最好要冷静观察当前的进度变化情况。
                //加了@Transactional后 逻辑完全不同！
                jobm.setAuid("请再三确认分片偏移量已经不再继续移动");
                jsliceMangRepository.save(jobm);     //挪到jobm.setAuid("请再三；  后面
                return jobm;
            }
            if(!jobm.getFireor().getId().equals(user.getId()))
            {
                jobm.setDesc(String.format("任务被抢占了,太久%d秒", duration.toSeconds()));
                return jobm;
            }
            //Auid验证过了:允许实际时间超出1分钟，可持续占用,但有危险可能被抢占任务【并发执行机会】; 前端程序没有响应超时之后就能解锁。
            jobm.setAuid(uuid);
            jobm.setLast(now);
             //  jsliceMangRepository.save(jobm); 不能两次save导致version乐观锁冲突报错； version感觉没用到？
        }else{
            jobm.setAuid(uuid);
            //  jsliceMangRepository.save(jobm);  不能两次save导致version乐观锁冲突报错；
        }
        int enterOffs =jobm.getOffs();
        Boolean syncOK=true;
        JsliceMang retjm=new JsliceMang(jobm.getName(),jobm.getOffs(),jobm.getLimt(), LocalDateTime.now());      //非数据库的，不涉及事务=就不报错。
        //【切分单步任务】真分片作业部分：【待机掉电?导致中断】场合：部分保存DB后续网络失败fail='读实体失败1',offs应当后退一个limt才能确保数据完整。
        if("OD".equals(type))    syncOK=makeSomeForOD(jobm,arg);
        else if("Su".equals(type)){
            retjm=constructUnitSu(retjm,arg);        //?内部单独开启事务
            if(StringUtils.hasText(retjm.getDesc()))   syncOK=false;
            else  jobm.setOffs(retjm.getOffs());
        }
//        else if("AK".equals(type))    syncOK=syncEqpFromLegacy_AK(jobm,arg);
        else if("AU".equals(type))    syncOK=syncEqpFromLegacy_AU(jobm,arg);
        else if("SI".equals(type))    syncOK=makeSomeForSI(jobm,arg);
        else if("DT".equals(type))    syncOK=makeSomeForDT(jobm,arg);
        else if("RL".equals(type))    syncOK=makeSomeForRL(jobm,arg);
        else if("VF".equals(type))    syncOK=makeSomeForVF(jobm,arg);
        else if("Dm".equals(type))    syncOK=makeSomeForDm(jobm,arg);
        else if("Pi".equals(type))    syncOK=makeSomeForPi(jobm,arg);
        else Assert.isTrue(false,"未定义type:"+type);
        log.info("syncEqpFromOld:Finish，type={}",type);
        LocalDateTime now2= LocalDateTime.now();
        //没有事务Transactional约束：不同客户看到不同的jobm,jobm实际由其他线程已经修改了还不知道啊。同一个客户findByName2俩次看到一样的对象。
        jobm.setLast(now2);
        if(enterOffs>=jobm.getOffs()){        //有可能Offs比预定的jobm.totl()跑得更远。
            jobm.setDesc("无法前进offset");     //实际已经完成{分片偏移后无法找到更多条的数据记录}
            jobm.setAuid(null);
        }
        if(!syncOK)  jobm.setAuid(null);    //终止执行 报错暂停; CRDB经常都会报错，都会暂停!!
        //被constructUnitSu函数修改: 报错StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) :.JsliceMang
        jsliceMangRepository.save(jobm);
        //optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException:
        ackjm.setOffs(jobm.getOffs());
        ackjm.setAuid(jobm.getAuid());
        ackjm.setFireor(jobm.getFireor());
        ackjm.setDesc(jobm.getDesc());
        return ackjm;     //此时的jobm内容没有保存数据库的，只是给前端看的。
    }
    //维护　建地级市表
    public Boolean syncUnitFromLeg_AD建地级市表() {
        Iterable<Province>  pall= provinceRepository.findAll();
        AtomicInteger sum= new AtomicInteger();
        for (Province province:pall) {
            QDictArea qm = QDictArea.dictArea;
            BooleanBuilder builder = new BooleanBuilder();
            DictArea dictArea=dictAreaRepository.getReferenceById(province.getOldId());     //省份回溯编码
            builder.and(qm.FAU_TYPE_PARENT_CODE.eq(dictArea.getFAU_TYPE_CODE()));
            Iterable<DictArea> list = dictAreaRepository.findAll(builder);
            list.forEach(item -> {
                City mone = new City();
                mone.setProvince(province);
                mone.setName(item.getFAU_TYPE_NAME());
                mone.setOldId(item.getId());
                cityRepository.save(mone);
                sum.getAndIncrement();
                log.info("地级市:{}{} id={}", province.getName(),item.getFAU_TYPE_NAME(), mone.getId());
            });
        }
        log.info("AD建地级市:Fin 合计{}",sum);
        return true;
    }
    //维护 建省份表
    public Boolean syncUnitFromLeg_AD建省份表() {
        Country country=countryRepository.findByName("中国").orElse(null);
        Assert.isTrue(null!=country,"奇");
        QDictArea qm = QDictArea.dictArea;
        BooleanBuilder builder = new BooleanBuilder();
        builder.and(qm.FAU_TYPE_PARENT_CODE.eq("1"));   //1=中国
        AtomicInteger sum= new AtomicInteger();
        Iterable<DictArea> list = dictAreaRepository.findAll(builder);
        list.forEach(item -> {
            Province province=new Province();
            province.setCountry(country);
            province.setName(item.getFAU_TYPE_NAME());
            province.setOldId(item.getId());
            provinceRepository.save(province);
            sum.getAndIncrement();
            log.info("shenfen:{} id={}", item.getFAU_TYPE_NAME(), province.getId());
        });
        log.info("sync建省份:Fin {}", sum);
        return true;
    }
    /**【人工改个半死】建区县表; 直接对源头表tb_dict_area3做改动！【人工修正】旧平台抄来的区划代码基础表中有重复数据=报错！
     * fau_type_name like '%新巴尔虎%'
     * 内蒙古自治区呼伦贝尔市新巴尔虎- 重名 人工改成：150727右旗  150726左旗; 内蒙古自治区乌兰察布盟察哈尔右-237新疆维吾尔自治区巴音郭楞蒙古自-231
     * 新疆维吾尔自治区昌吉回族自治州-229新疆维吾尔自治区博尔塔拉蒙古自-230新疆维吾尔自治区伊犁哈萨克自治-218新疆维吾尔自治区克孜勒苏柯尔克-215
     select * from fjtj.public.tb_dict_area where fau_type_name in(select fau_type_name from(
       select fau_type_parent_code,fau_type_name,count(*) from fjtj.public.tb_dict_area group by fau_type_parent_code,fau_type_name having count(*)>1 ))
        order by fau_type_name
     查询并修改确保重复清零；
     * */
    public Boolean syncUnitFromLeg_AD建区县表() {
        AtomicInteger sum= new AtomicInteger();
        Iterable<City>  pall= cityRepository.findAll();
        for (City parent:pall) {
            QDictArea qm = QDictArea.dictArea;
            BooleanBuilder builder = new BooleanBuilder();
            DictArea dictArea=dictAreaRepository.getReferenceById(parent.getOldId());     //回溯编码
            builder.and(qm.FAU_TYPE_PARENT_CODE.eq(dictArea.getFAU_TYPE_CODE()));
            Iterable<DictArea> list = dictAreaRepository.findAll(builder);
            list.forEach(item -> {
                County mone = new County();
                mone.setCity(parent);
                mone.setName(item.getFAU_TYPE_NAME());
                mone.setOldId(item.getId());
                try {
                    countyRepository.save(mone);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                sum.getAndIncrement();
                log.info("区县:{}>{} id={}", parent.getName(),item.getFAU_TYPE_NAME(), mone.getId());
            });
        }
        log.info("建区县表:Fin sum={}", sum);
        return true;
    }
    //事务失败，拆分：两个小步骤做
    public Boolean syncUnitFromLeg_AD街道乡镇p(String type) {
        AtomicInteger sum= new AtomicInteger();
        Iterable<County>  pall= countyRepository.findAll();
        for (County parent:pall) {
            QDictArea qm = QDictArea.dictArea;
            BooleanBuilder builder = new BooleanBuilder();
            DictArea dictArea=dictAreaRepository.getReferenceById(parent.getOldId());     //回溯编码
            parent.setParcod(dictArea.getFAU_TYPE_CODE());
            countyRepository.save(parent);
        }
        log.info("街道乡镇表:1步溯上级cod Fin={}", sum);
        return true;
    }
    //[目前只有福建省的]街道乡镇表
    public Boolean syncUnitFromLeg_AD街道乡镇(String type) {
        AtomicInteger sum= new AtomicInteger();
        Iterable<County>  pall= countyRepository.findAll();
        for (County parent:pall) {
            QDictArea qm = QDictArea.dictArea;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.FAU_TYPE_PARENT_CODE.eq(parent.getParcod()));
            Iterable<DictArea> list = dictAreaRepository.findAll(builder);
            list.forEach(item -> {
                Town mone = new Town();
                mone.setCounty(parent);
                mone.setName(item.getFAU_TYPE_NAME());
                mone.setOldId(item.getId());
                try {
                    townRepository.save(mone);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                sum.getAndIncrement();
                log.info("街镇:{}>{} id={}", parent.getName(),item.getFAU_TYPE_NAME(), mone.getId());
            });
        }
        log.info("街道乡镇表: Fin={}", sum);
        return true;
    }
    //从Town自动构造Adminunit, 行政管理单元：高级别的行政管理单元？
    public Boolean syncUnitFromLeg_AD管理单元(String type) {
        AtomicInteger sum= new AtomicInteger();
        Iterable<Town>  pall= townRepository.findAll();
        for (Town parent:pall) {
            Adminunit adminunit=adminunitRepository.findByTownIs(parent); //不允许嵌套关系的Adminunit存在，不应该有大小级别区域共存。
            if(null==adminunit) {       //旧的，可重新执行模式，分片+已经初始化
                adminunit = new Adminunit();
                adminunit.setTown(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);  //不同数据库的
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setCounty(parent.getCounty());
            adminunit.setCity(parent.getCounty().getCity());
            adminunit.setProvince(parent.getCounty().getCity().getProvince());
            adminunit.setCountry(parent.getCounty().getCity().getProvince().getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);    //不能用saveAndFlush(),执行很慢的！！
            sum.getAndIncrement();
            log.info("行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        log.info("构建管理单元:Fin sum={}", sum);
        return true;
    }

    /**国家表：只有名称*/
    public Boolean syncUnitFromLeg_AD第二步(String type) {
        QDictArea qm = QDictArea.dictArea;
        BooleanBuilder builder = new BooleanBuilder();
        //国家代码 fau_type_parent_code='2' #特例 1,0,中国
        builder.and(qm.FAU_TYPE_PARENT_CODE.eq("2"));
        ArrayList<DictArea> list =(ArrayList)dictAreaRepository.findAll(builder);
        AtomicInteger countall= new AtomicInteger();
        log.info("找国家:type={},条数{}",type, list.size());
        list.forEach(item -> {
            Country country=new Country();
//if(item.getFAU_TYPE_NAME().startsWith("国外")) country.setName(item.getFAU_TYPE_NAME().substring("国外".length()));else
            country.setName(item.getFAU_TYPE_NAME());
            countryRepository.save(country);
            countall.getAndIncrement();
        });
        Country country=new Country();
        country.setName("中国");
        countryRepository.save(country);
        log.info("syncUnitFromLeg_AD第二步:Fin {}", countall);
        return true;
    }

    public Boolean syncUnitFromLeg_AD(String type,String arg) {
        if("Al".equals(arg))    return syncUnitFromLeg_AD第一步(type);
        else if("cn".equals(arg))    return syncUnitFromLeg_AD第二步(type);
        else if("Pv".equals(arg))    return syncUnitFromLeg_AD建省份表();
        else if("Ct".equals(arg))    return syncUnitFromLeg_AD建地级市表();
        else if("qX".equals(arg))    return syncUnitFromLeg_AD建区县表();
        else if("Tp".equals(arg))    return syncUnitFromLeg_AD街道乡镇p(type);
        else if("TW".equals(arg))    return syncUnitFromLeg_AD街道乡镇(type);
        else if("Ex".equals(arg))    return syncUnitFromLeg_AD例外处理(type);
        else if("dY".equals(arg))    return syncUnitFromLeg_AD管理单元(type);
        else if("GJp".equals(arg))    return syncUnitFromLeg_AD管理单元GJp(type);
        else if("GJc".equals(arg))    return syncUnitFromLeg_AD管理单元GJc(type);
        else if("Lb".equals(arg))    return syncUnitFromLeg_AD导楼盘小区();
        return true;
    }
    /**刚生成的乡镇Town表，有些数据古怪，要修正下。 name like '%及所属范围'
     * 及各村
     * */
    public Boolean syncUnitFromLeg_AD例外处理(String type) {
        AtomicInteger sum= new AtomicInteger();
        Iterable<Town>  pall= townRepository.findAll();
        for (Town parent:pall) {
            String name=parent.getName();
            if(name.endsWith("及各村")) {
                parent.setName(name.substring(0, name.length()-3));
                townRepository.save(parent);
                sum.getAndIncrement();
                log.info("街镇:{}->{} id={}", name, parent.getName(), parent.getId());
            }
        }
        log.info("乡镇表有些古怪，要修正: Fin={}", sum);   //110条数据：漳州的*
        return true;
    }
    //监察永久单位表 簡單映射到新平台Unit; 传递<T>=SliceSyncRes=同步作业目标实体单条记录。
    public <T> Triple constructUnit_Su监察批Pm(T inObj) {
        String mesg="";
        JcPermtUnt iden= (JcPermtUnt)inObj;
        if( (null!=iden.getIS_DEL() && iden.getIS_DEL()) || !StringUtils.hasText(iden.getName()))
            return new Triple("无效", null, null);
        //這個類型單位，多數是企業
        boolean isPerson=iden.getName().length()<=3 ||
                     (iden.getORG_COD().length()==18 && iden.getORG_COD().startsWith("35") );
        if(!isPerson){
            Company company= companies.findByName(iden.getName());
            if(null!=company){    //不一定必须在监察的3个单位表里面找到；若仅仅在检验单位表有的是也能匹配的;
                iden.setUunit(units.findUnitByCompany(company).getId());
                return new Triple(mesg, null, null);
            }
        }else{
            Person person= persons.findByNo(iden.getORG_COD());
            if(null!=person){
                iden.setUunit(units.findUnitByPerson(person).getId());
                return new Triple(mesg, null, null);
            }
        }
        //历史原因！输入起点，用户可随意输入企业名字，名字差错多。 湖北长江石化设备有限公司:特别！监察3表没找到，可检验有啊，所以PMT永久表映射成功。
        return new Triple("找不到", null, null);    //考虑新new Unit()? 单位表实际是外部数据源，并非本平台自己家维护！
    }
    /**只好再次拆分:除了Town/Country级别; 给高级别的(省/市/县)构造Adminunit行政管理单元
     * */
    public Boolean syncUnitFromLeg_AD管理单元GJp(String type) {
        int sum=0;
        Iterable  pall= provinceRepository.findAll();
        for (Province parent: (Iterable<Province>)pall) {
            Adminunit adminunit=adminunitRepository.readAdminunitByProvinceAndAndCity(parent,null);  //最多返回一个
            if(null==adminunit) {
                adminunit = new Adminunit();
                adminunit.setProvince(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setCountry(parent.getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);
            sum++;
            log.info("省级行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        sum=0;
        pall= cityRepository.findAll();
        for (City parent: (Iterable<City>)pall) {
            Adminunit adminunit=adminunitRepository.readAdminunitByCityAndAndCounty(parent,null);  //最多返回一个
            if(null==adminunit) {
                adminunit = new Adminunit();
                adminunit.setCity(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setProvince(parent.getProvince());
            adminunit.setCountry(parent.getProvince().getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);
            sum++;
            log.info("地市行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        log.info("构建GJ管理单元:Fin sum={}", sum);
        return true;
    }
    /*HibernateSearch导致Adminunit被事后处理会影响性能！。 HS后处理费时间是在事务提交完了后续做的！
    另外：增加索引与否极大影响。 【事先确保】 adminunit_county_id_town_id_index索引的建立了吗？
    * */
    public Boolean syncUnitFromLeg_AD管理单元GJc(String type) {
        int sum=0;
        Iterable  pall= countyRepository.findAll();
        for (County parent: (Iterable<County>)pall) {
            Adminunit adminunit=adminunitRepository.readAdminunitByCountyAndTown(parent,null);  //最多返回一个
            if(null==adminunit) {
                adminunit = new Adminunit();
                adminunit.setCounty(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setCity(parent.getCity());
            adminunit.setProvince(parent.getCity().getProvince());
            adminunit.setCountry(parent.getCity().getProvince().getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);
            sum++;
            log.info("县区级行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        log.info("构建GJ管理单元:Fin sum={}", sum);
        return true;
    }
    public <T> Triple<StoreSyncRepository,Object,Object>  syncEqp单设备OD并发(T inObj, String arg) {
        StoreSync parb= (StoreSync)inObj;
        try {
            String result="";//getDeviceFromOld(parb, arg);
            return new Triple(result, null, null);
        } catch (Exception e) {
            e.printStackTrace();
            String trun = e.getMessage().length() > 250 ? e.getMessage().substring(0, 250) : e.getMessage();
            throw new CommonGraphQLException("inf", e.getMessage());
//            return new Triple("inf"+trun, null, null);
        }
    }
    /**
     * OD作业并发 #totl设置OD.1=175000 /350000 /525000 /700000 而OD.5=910000; offs设置=上一个线程totl;
     * 并发多线程方式：arg=“.1”等； offset=N开始代表前面省略掉N条的记录。展示层显示第N+1条正式启动；totl标识最后一次Loop,实际作业可允许超过total对应的那一条记录。
    * */
    public Boolean makeSomeForOD(JsliceMang jobm,String arg) {
        Function<StoreSync, Triple<?,?,?>>  sliceJob= parb ->{
//            if("OK".equals(parb.getFail()))
//                return null;
            if(".1".equals(arg) || ".2".equals(arg) || ".3".equals(arg) || ".4".equals(arg) || ".5".equals(arg) )
                return syncEqp单设备OD并发(parb, arg);
            else return null;
        };
        //后面StoreSync.class是实体投影；
        return jobm.runChannelSlice(storeSyncRepository, sliceJob, null, null,StoreSync.class);
    }
    //测试：
    public <T> Triple  makeFor单个SI维护bC(List<EqpEs> packs) {
        EqpEs inObj= packs.get(0);
        String retRes="";
        UUID eqpid= inObj.getId();    //？？泛型==欺骗编译器;
        log.info("排序asc作业EqpEs {},{} {}:{}", inObj.getUseu().getId(), inObj.getNxtd2(),inObj.getNxtd1(), inObj.getCod());
        //事务当中查询出来的数据库实体：是可以自动保存的,new()不算。
        return new Triple(retRes, null, null);
    }
    /**单个包的设备看需要生成任务吗: 使用单位和(下次)日期一样的。
     * 参数jyzq: 检验类型： 0=全面定期检验， 1=年度检验，在线检验的业务；
     * */
    public <T> Triple  makeFor单个SI任务生成(List<EqpEs> packs,int jyzq) {
        String retRes="";
        EqpEs base=packs.get(0);      //包的 单位和任务日期getNxtd2/1()不变
        LocalDate today= LocalDate.now();
        BusinessCat_Enum  baseBusType= BusinessCat_Enum.REGUL;
        LocalDate  basejyDate= base.getNxtd2();
        if(1==jyzq) {
            baseBusType= BusinessCat_Enum.ANNUAL;
            basejyDate= base.getNxtd1();
        }
        QTask qm = QTask.task;
        Sort sort=Sort.by(Sort.Order.asc("date"));       //【不可省略】顺序排列
        BooleanBuilder builder = new BooleanBuilder();
        BooleanBuilder statusbuild = new BooleanBuilder();
        statusbuild.or(qm.status.eq(TaskState_Enum.DONE)).or(qm.status.eq(TaskState_Enum.CANCEL));
        builder= builder.and(qm.servu.id.eq(base.getUseu().getId())).andNot(statusbuild).and(qm.date.between(today,today.plusYears(1)))
                .and(qm.crman.isNull()).and(qm.bsType.eq(baseBusType));
        //旧的任务已经完成但是下检日期异常没同步的？旧任务的日期不在这后1年之内的而且还没终结的拖延的异常？（延期）要修改任务日。可能差错范畴。人工生成单子的。
        List<Task> tpool= (List<Task>) taskRepository.findAll(builder,sort);
        int poolOldSize= tpool.size();          //后面本次新生成任务可在tpool末尾加入！
        List<Task> tsnew=new ArrayList<>();       //新增加的任务
        Unit objServu= units.findById(base.getUseu().getId()).orElse(null);
        Assert.isTrue(null!=objServu, "无效单位"+base.getUseu().getId());
        log.info("SC{}:{}#{}${}" ,objServu.name(),basejyDate,tpool.size(), packs.stream().map(a->a.getCod()).collect(Collectors.toList()) );
        LocalDate finalBasejyDate = basejyDate;
        Function<Integer, Integer>  callback = val -> {
            return (int)((tpool.get(val).getDate().toEpochDay() - finalBasejyDate.toEpochDay()));
        };
        //从任务缓冲区挑选适合和当前下检日期吻合的可能任务：合理时间区间之内=管道前后60天定检，其他设备180天定检。依据来认定为同一次目标检验任务，避免重复生成。
        int innerDays = 60,  outerDays= 180;
        if(1==jyzq){             //年度检验:
            innerDays= 30;          //管道的特别 30天;
            outerDays= 90;
        }
        int resta[]= Tool.sortListMatchTwoYz(tpool.size()-1, callback,innerDays,outerDays);
        //准备好 60 : 180 天范围之内任务列表集合:两个范围区。 {管道+-1，年度+-3, #定检+-6 管道+-2，}匹配;
        int finalGuessofs1 = resta[1];
        int finalGuessend1 = resta[2];
        int finalGuessofs2 = resta[0];
        int finalGuessend2 = resta[3];
        BusinessCat_Enum finalBaseBusType = baseBusType;
        packs.forEach(eqpEs->{
            Assert.isTrue(base.getUseu().getId().equals(eqpEs.getUseu().getId()) && (0==jyzq && finalBasejyDate.equals(eqpEs.getNxtd2())
                   || 1==jyzq && finalBasejyDate.equals(eqpEs.getNxtd1()) ),"算法问题");
            Eqp eqp= eQPRepository.findById(eqpEs.getId()).orElse(null);
            Division objDep=null;
            if(null!=eqpEs.getIspud())
                objDep= divisionRepository.findById(eqpEs.getIspud().getId()).orElse(null);
            if(null==objDep)    return;           //无效只能放弃！【前置的步骤的该干的事情】 部门都没设置啊！ 这儿不管。
            Unit objIspu=objDep.getUnit();        //不能直接依据eqpEs.getIspu(),  数据管理者不同！ 转义处理#
            Boolean  entrust= eqpEs.getOcat() || eqpEs.getReg().equals(RegState_Enum.TODOREG);
            Task  meet=null;
            Task  coincidence=null;     //最符合的任务：重复运行？同一批次的;
            TaskState_Enum lastCoinciSta= TaskState_Enum.CANCEL;        //最大的数值！
            //单个Eqp：可以放入某一个Task吗?{for task in tpool+new()}
            Boolean inGD= eqpEs.getType().equals("8");       //若管道设备的：合理时间时间区间更短暂！
            for (int i = 0; i < tpool.size(); i++) {
                //有必要搜索区间：
                if( i>=poolOldSize || (inGD && finalGuessofs1 >=0 && i>= finalGuessofs1 && i<= finalGuessend1)
                        || (!inGD && finalGuessofs2 >=0 && i>= finalGuessofs2 && i<= finalGuessend2)
                ) {
                    Task task = tpool.get(i);
                    //竟然task.getDep().equals(objDep)为假 但是task.getDep().getId().equals(objDep.getId())为真;
                    if(task.getIspu().getId().equals(objIspu.getId()) && task.getDep().getId().equals(objDep.getId()) && task.getEntrust().equals(entrust) ){
                        //多次出现某一个Eqp的同一种检验业务任务？  in｛ dets[].isp.dev.id=? ｝
                        if(task.getDets().stream().anyMatch(det ->
                                null!=det.getIsp() && null!=det.getIsp().getDev() && eqpEs.getId().equals(det.getIsp().getDev().getId()) )) {
                            meet = task;          //已经登记了业务，可能任务日期:不等于预期(下检)日期？
                            break;       //假定这么短时间内不会出现两次自动生成任务
                        }
                        else {
                            int xcdays= (int) (task.getDate().toEpochDay() - finalBasejyDate.toEpochDay());
                            if(0 == xcdays && TaskState_Enum.DISP!=task.getStatus() && TaskState_Enum.HANGUP!=task.getStatus()) {
                                if(lastCoinciSta.compareTo(task.getStatus()) >= 0 ) {
                                    coincidence = task;       //凑巧同一天的任务 & 允许新添加设备的。
                                    lastCoinciSta= task.getStatus();
                                }
                            }
                        }
                    }
                }
            }
            //合理时间区间之内没有遇见该设备的：代表可能要增加新任务或新设备到就任务的。
            if(null==meet){
                //虽然Eqp没有在可融合的任务当中，但是若有任务是碰巧能够接纳该设备？
                Detail detail=new Detail();
                Isp isp=Isp.builder().dev(eqp).servu(objServu).ispu(objIspu).bsType(finalBaseBusType).entrust(entrust).bus(detail).build();
                iSPRepository.save(isp);
                detail.setIsp(isp);
                if(null==coincidence) {          //无法仰仗旧的任务单的。 人工生成单子也不算：去重复只能放在分配派工环节剔除。
                    List<Detail> busList = new ArrayList<>();
                    busList.add(detail);
                    Task task = Task.builder().bsType(finalBaseBusType).servu(objServu).entrust(entrust).status(TaskState_Enum.INIT)
                            .date(finalBasejyDate).ispu(objIspu).dep(objDep).crtime(LocalDateTime.now()).dets(busList).build();
                    detail.setTask(task);
                    details.save(detail);        //保存这个时间task.id还没有啊?  事务
                    tpool.add(task);         //@新增加放入任务缓冲区末尾。
                    tsnew.add(task);
                }
                else{        //凑巧同一天的任务 & 已经派工的不能加：只能新生成new
                    coincidence.getDets().add(detail);
                    detail.setTask(coincidence);
                    details.save(detail);
                }
            }
        });
        //不做保存：报object references an unsaved transient instance save the transient instance before flushing : md.specialEqp.inspect.Detail.task -> md.specialEqp.inspect.Task
        taskRepository.saveAll(tsnew);
        return new Triple(retRes, null, null);       //批量保存不能用，本事务再次taskRepository.findAll(builder,sort)出错！
    }
    /**本系统的检验机构用户：
     * */
    public Boolean setupFromLegacy_Pz部门生成(String arg) {
        if(!emFjtj.isJoinedToTransaction())     emFjtj.joinTransaction();   //依照HR部门生成导Division
        Unit meu= units.findUnitByCompany_Name("福建省特种设备检验研究院");
        Assert.isTrue(null!=meu,"没初始化");
        List<HrDeptinfo> result=hrDeptinfoRepository.findAll();
        int count=0;
        for (HrDeptinfo one: result)
        {
            if(one.getStatus()!=1 || null!=one.getParentId())  continue;
            Division division= divisionRepository.findByUnitAndName(meu, one.getDEPT_NAME());
            if(null==division)  division=new Division();
            Adminunit adminunit=adminunitRepository.findTopByAreacode(one.getAREA_COD());
            division= division.toBuilder().name(one.getDEPT_NAME()).unit(meu).address(one.getDEPT_ADD())
                    .linkMen(one.getDEPT_PRINC()).phone(one.getDEPT_TEL()).ad(adminunit)
                    .branch(false).frot("闽HR").build();
            divisionRepository.save(division);    //不能用divisionRepository.saveAll(batch)；  JPA关联关系不能跨越多个数据库.
            one.setDivision(division.getId());
            count++;
        }
        //【无法用】获取不到.setDivision(division.id); divisionRepository.saveAll(batch);  // divisionRepository.flush();
        //第一次运行division.getId()还是空的;会导致需要运行两遍才能保存： 跨越数据库情况！ .id(UUID.randomUUID())
        hrDeptinfoRepository.saveAll(result);
        log.info("特检院的部门: 生成条数{}", count);
        return true;
    }
    /**【缺点】存储库实体表必须implements SliceSyncRes；
     * 分片作业+批量保存的模式； mng是输入状态，jsliceMang代表修改的状态；
     * 同时兼容ElasticsearchRepository<T, ID>以及JpaRepository<T, ID>;在boot3.0版本需要区分加入< ,RP>>类型；
     * 泛型矛盾检查： 这里的T可能这样的JcPermtUnt implements SliceSyncRes； 所以T extends SliceSyncRes,以及Function<SliceSyncRes, Triple> sliceJob,
     * 避免和后面RP extends PagingAndSortingRepository<T,?> & CrudRepository<T,?>冲突！！
     * */
    public <T extends SliceSyncRes, RP extends PagingAndSortingRepository<T,?> & CrudRepository<T,?>> JsliceMang  sliceJobBatch(JsliceMang mng,RP pagingAndSortingRepository,
                Function<SliceSyncRes, Triple> sliceJob, CrudRepository repository1, CrudRepository repository2){
        Pageable pageable= PageOffsetFirst.of(mng.getOffs(),mng.getLimt());
        //分片任务，保证可以重复执行，确保findAll读取出来的记录有顺序。
        log.info("run:作业{},Off={}", mng.getName(), mng.getOffs());
        Iterable<T>  pall= pagingAndSortingRepository.findAll(pageable);
        int count=0;
        List rpl1=new ArrayList<>();
        List rpl2=new ArrayList<>();
        for (T parent:pall) {
            Triple triple=null;
            Boolean execOK=true;
            try {
                //返回需要批量存储仓库的实体1，2：
                triple=sliceJob.apply(parent);
                String result= (String)triple.getVal1();
                //错误提示result，不代表绝对没有生成可用数据，有些仅仅算提示。
                if(StringUtils.hasText(result) )
                    ((SliceSyncRes)parent).setFail(result);
                else
                    ((SliceSyncRes)parent).setFail(null);
                Object enty1= triple.getVal2();
                if(null!=enty1)     rpl1.add(enty1);
                Object enty2= triple.getVal3();
                if(null!=enty2)     rpl2.add(enty2);
            } catch (Exception e) {
                e.printStackTrace();
                execOK= false;
            }
            if(!execOK){
                log.error("报错暂停,该后退一个limit; offs={}重来",mng.getOffs());     //还会出现的 错误种类太多了
                mng.setDesc("报错暂停,该后退一个limit");   //报错暂停应该后退一个limit
                //mng.setOffs(mng.getOffs() + count);
                //取消批量存储？ 一整批抛弃？原先最多弃单个
                return mng;
            }
            count++;
            LocalDateTime now=java.time.LocalDateTime.now();
            java.time.Duration duration = java.time.Duration.between(mng.getLast(), now);
            //怕时间太长，影响任务抢占了。分片运行有效时间太短了反而没机会抢到。
            if(duration.toSeconds()>60)
                break;
        }
        //最后一批一起保存。 ？把ES批量保存和数据库事务拆开，CRDB事务只管数据库的作业，ES保存时间要拖出去。
        if(null!=repository1)   repository1.saveAll(rpl1);   //居然5秒就保存超时失败导致分片任务终止
        if(null!=repository2)   repository2.saveAll(rpl2);
        pagingAndSortingRepository.saveAll(pall);
        mng.setOffs(mng.getOffs() + count);
        return mng;
    }
    /**ES: 分片作业 更新ES等 , 根据EqpES生成下年检验任务。
     * 下面"bC" 或 eqpEsRepository 和 makeFor单个ES维护bC ： @都已经淘汰，可删除！！
     * */
    @Transactional(readOnly=true)
    public Boolean makeSomeForSI(JsliceMang jobm,String arg) {
        LocalDate today= LocalDate.now();
        Function<?, Triple<?,?,?>>  sliceJob= parb ->{
            //【泛型】问题：运行期才会报错class java.util.ArrayList cannot be cast to class org.fjsei.yewu.index.EqpEs；旧的makeFor单个SI维护bC((EqpEs) parb);
            if("bC".equals(arg))    return makeFor单个SI维护bC((List<EqpEs>) parb);
            else if("Ts".equals(arg))    return makeFor单个SI任务生成((List<EqpEs>) parb, 0);
            else if("ND".equals(arg))    return makeFor单个SI任务生成((List<EqpEs>) parb, 1);
            else return null;
        };
        //注入：合并打包的规则, 为每个包运行sliceJob()一次。
        BiFunction<EqpEs, EqpEs, Boolean>  packFunc= (base, cmpob)->{
            if("Ts".equals(arg)) {
                return (base.getUseu().equals(cmpob.getUseu()) && base.getNxtd2().equals(cmpob.getNxtd2()) );
            }
            else if("ND".equals(arg)){
                return ( base.getNxtd1().equals(cmpob.getNxtd1()) && base.getUseu().equals(cmpob.getUseu()) );
            }
            return false;       //不合并的
        };
        //因为spring-data-elasticsearch-5版本SearchAfter不支持Date的类型;统一搞都FieldValue.of(Kind.String)
        BiFunction<Object, Boolean, Object>  searchAfterCvtFn= (from, str2List)->{
            if("Ts".equals(arg) || "ND".equals(arg)) {
                if(str2List){
                    return JSON.parseObject((String) from, List.class);
                }else {
                    List<Object> afterObj= (List<Object>) from;
                    List<Object> newList=new ArrayList<>(afterObj);
                    newList.set(1, longstr2Date((String) afterObj.get(1)));
                    return JSON.toJSONString(newList);
                }
            }
            else if("bC".equals(arg)){
                if(str2List){
                    return JSON.parseObject((String) from, List.class);
                }else {
                    List<Object> afterObj= (List<Object>) from;
                    List<Object> newList=new ArrayList<>(afterObj);
                    newList.set(1, longstr2Date((String) afterObj.get(1)));
                    newList.set(2, longstr2Date((String) afterObj.get(2)));
                    return JSON.toJSONString(newList);
                }
            }
            return null;
        };
        Pageable pageable;
        if("bC".equals(arg)){       //仅仅测试目的：ES8从API获取的是string long表达的日期，要设置的输入的却要求是“uuuu-MM-dd”，中间经过spring-data-elasticsearch搞得鬼。
            List<Query> listquerys = new ArrayList<Query>();
            //【报错】因为用了"_id"排序聚合 “Fielddata access on the _id field is disallowed, you can re-enable it by updating the dynamic cluster setting: indices.id_field_data.enabled”
            //_id字段的值可以用来进行聚合和排序，但不鼓励这样做，因为它需要在内存中加载大量数据。如果需要对_id字段进行排序或聚合，建议使用另外一个字段来存储与_id一样的值;
            //_id字段被禁止在聚合、排序和脚本中使用。如果需要对_id字段进行排序或聚合，建议将_id字段的内容复制到doc_values的字段中。
            //ES8排序字段"_id"被禁用会报错了，只好多两个（cod + ispu.id）来接替了，确保分页的排序和全量数据检索严谨性; 也不能用"_uid"；
            //Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd2"),Sort.Order.asc("nxtd1"),Sort.Order.asc("_id"));
            Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd2"),Sort.Order.asc("nxtd1"),Sort.Order.asc("cod_sort"),Sort.Order.asc("ispu.id"));
            pageable = PageOffsetFirst.of(0, jobm.getLimt(), sort);
//            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withQuery(
//                    boolQuery().must(
//                            matchPhraseQuery("sort", "31").slop(7)
//                    )
//            ).withPageable(pageable);
//            NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder().withPageable(pageable);
            //本身 SELF<>  BaseQueryBuilder<Q, SELF> builder=; 所以可以直接变成的
            NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
                    .withFilter(q -> q.bool(
                                    v->v.must(listquerys)
                            )
                    );
            NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
            return searchAfterEsP(jobm, query, sliceJob, null, null,EqpEs.class,packFunc,searchAfterCvtFn);
        }
        else if("Ts".equals(arg)){
            List<Query> listquerys = new ArrayList<Query>();
            Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd2"),Sort.Order.asc("cod_sort"),Sort.Order.asc("ispu.id"));
            pageable = PageOffsetFirst.of(0, jobm.getLimt(), sort);
            //前置限制 6个基本过滤 nxtd2 >='2022-08-17' and  nxtd2<='2023-08-17' 数据量大大减少
            //【严重】下检日都过了还没任务启动的不应该放在这里去生成任务： 超期未检的应该从监察平台监控；本平台前面步骤可预处理这种；【假设】我这环节没有超期未检设备。
//            旧版本 NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder().withQuery(
//                    boolQuery().must( rangeQuery("nxtd2").gte(today).lte(today.plusYears(1)) )
//                            .must(new ExistsQueryBuilder("useu.id")).must(new ExistsQueryBuilder("svu.id")).must(new ExistsQueryBuilder("ispu.id"))
//                            .mustNot(new TermsQueryBuilder("reg", "CANCEL")).must(new TermsQueryBuilder("ust", "USE","USENOTREG"))
//            ).withPageable(pageable);
            listquerys.add(Query.of(q -> q.range(RangeQuery.of(rq -> rq.date(DateRangeQuery.of(c -> c
                            .field("nxtd2")
                            .from(String.valueOf(today))
                            .to(String.valueOf(today.plusYears(1)))
                    ))
            ))));
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("useu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("svu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("ispu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .terms(m -> m
                            .field("ust").terms(k->k
                                    .value(List.of(FieldValue.of("USE"), FieldValue.of("USENOTREG")))
                            )
                    ) )
            );
            //不应嵌入这里？ .mustNot(new TermsQueryBuilder("reg", "CANCEL"))  ;可惜Query.of(c -> c.真没法直接套接must mustNot should()的！！必须加一层的.bool();bool做顶层或聚合逻辑层。逻辑嵌套模式;
            //【注意】两个同名字方法的实际来自不同包！前面是 co.elastic.clients.elasticsearch._types.query_dsl.Query.Builder.terms()后面是 co.elastic.clients.elasticsearch._types.query_dsl.TermsQuery.Builder.terms()
            listquerys.add(Query.of(c -> c
                            .bool(s -> s
                                    .mustNot(q->q
                                            .terms(m -> m
                                                    .field("reg").terms( k->k
                                                            .value(List.of(FieldValue.of("CANCEL")) )
                                                    )
                                            )
                                    )
                            )
                    )
            );
            NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
                    .withFilter(q -> q
                            .bool(v->v
                                    .must(listquerys)
                            )
                    );
            NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
            return searchAfterEsP(jobm, query, sliceJob, null, null,EqpEs.class,packFunc,searchAfterCvtFn);
        }
        else if("ND".equals(arg)){
            List<Query> listquerys = new ArrayList<Query>();
            Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd1"),Sort.Order.asc("cod_sort"),Sort.Order.asc("ispu.id"));
            pageable = PageOffsetFirst.of(0, jobm.getLimt(), sort);
//            NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder().withQuery(
//                    boolQuery().must( rangeQuery("nxtd1").gte(today).lte(today.plusYears(1)) )
//                            .must(new ExistsQueryBuilder("useu.id")).must(new ExistsQueryBuilder("svu.id")).must(new ExistsQueryBuilder("ispu.id"))
//                            .mustNot(new TermsQueryBuilder("reg", "CANCEL")).must(new TermsQueryBuilder("ust", "USE","USENOTREG"))
//            ).withPageable(pageable);
            listquerys.add(Query.of(q -> q.range(RangeQuery.of(rq -> rq.date(DateRangeQuery.of(c -> c
                            .field("nxtd1")
                            .from(String.valueOf(today))
                            .to(String.valueOf(today.plusYears(1)))
                    ))
            ))));
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("useu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("svu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("ispu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .terms(m -> m
                            .field("ust").terms(k->k
                                    .value(List.of(FieldValue.of("USE"), FieldValue.of("USENOTREG")))
                            )
                    ) )
            );
            //非多项的单个匹配可简化 .term(m -> m.field("reg").value("CANCEL")
            listquerys.add(Query.of(c -> c
                            .bool(s -> s
                                    .mustNot(q->q
                                            .terms(m -> m
                                                    .field("reg").terms( k->k
                                                            .value(List.of(FieldValue.of("CANCEL")) )
                                                    )
                                            )
                                    )
                            )
                    )
            );
            NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
                    .withFilter(q -> q
                            .bool(v->v
                                    .must(listquerys)
                            )
                    );
            NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
            return searchAfterEsP(jobm, query, sliceJob, null, null,EqpEs.class,packFunc,searchAfterCvtFn);
        }
        else return false;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    public Boolean setupFromLegacy_Ls临时Isp消除(String arg) {
        Pageable pageable= PageRequest.of(0, 999);
        QIsp q=QIsp.isp;        //Isp表设置dev_id可实际Eqp不存在的? q.dev.id.isNull()也不行啊:不正常数据，关联无效，逻辑？
        Predicate predicate= q.dev.cod.isNull().or(q.dev.oid.isNull());      //.or.导致只能是全表搜索的
        QBeanMy<?> piExp=new QBeanMy<Isp>(Isp.class, q.id );
        piExp.bindLeftJoin(q.dev);      //默认Cross join; 下面这条执行很慢！ 外键索引还没有建立？
        Slice<IspPi> slice= iSPRepository.findBy(piExp, predicate, (query)-> {
            FluentQuery.FetchableFluentQuery<IspPi> queryUse = query.as(IspPi.class);
            return  queryUse.page(pageable);
        });
        List<IspPi> result=slice.getContent();
        log.info("准备清理Isp错误Dev关联 条数{}", result.size());
        List<UUID> ids= result.stream().map(isppi->isppi.getId()).collect(Collectors.toList());
        iSPRepository.deleteAllByIdInBatch(ids);     //自己isp.ID删除也很慢? 删442条 em.flush()?
        return true;
    }
    //分片作业，Detail检验任务 更新等；
    //这底下的Function<DetailPi, Triple<?,?,?>>一定要标注类型,否则编译错。
    /**有id排序的漫步{存储库整表的翻页和搜索}，cursor游标方式:记住上一次的最后一行id;  #小的事务；但分片漫游无事务的保障。照样也会有漏网但是重复可免。
     * */
    public Boolean makeSomeForDT(JsliceMang jobm,String arg) {
        Function<Detail, Triple<?,?,?>>  sliceJob= parb ->{
            if("Oo".equals(arg))    return makeInf使用单位部门(parb);
            else return null;
        };
        if("Oo".equals(arg)){
            return searchAfter(jobm, details, sliceJob, null, null, Detail.class);
        }
        else return false;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    public Boolean makeSomeForRL(JsliceMang jobm,String arg) {
        Function<Company, Triple<?,?,?>>  sliceJob= parb ->{
            if("Bm".equals(arg))    return makeInf使用单位部门(parb);
            else if("Sd".equals(arg))    return makeInf使用单位机构分支(parb);
            else if("vL".equals(arg))    return makeInf导楼盘小区vL(parb);
            else return null;
        };
        Function<HouseMge, Triple<?,?,?>>  sliceJobHm= parb ->{
            if("vL".equals(arg))    return makeInf导楼盘小区vL(parb);
            else return null;
        };
        if("Bm".equals(arg)){
            //不加上，没法保存其他的数据库，非sei主库的表。
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return strollPage(jobm, companies, sliceJob, null, null, Company.class);
        }
        else if("Sd".equals(arg)){
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return searchAfter(jobm, companies, sliceJob, null, null, Company.class);
        }
        else if("vL".equals(arg)){
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return searchAfterL(jobm, houseMgeRepository, sliceJobHm, null, null, HouseMge.class);
        }
        else if("Of".equals(arg)){
            Function<HrDeptinfo, Triple<?,?,?>>  sliceJobOf= parb ->{
                return makeInf检验机构科室(parb);
            };
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return searchAfterL(jobm, hrDeptinfoRepository, sliceJobOf, null, null, HrDeptinfo.class);
        }
        else return false;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }

    /**关系数据库同步到ES搜索索引
     * 返回参数不一样，把作业结果直接返回。
     * */
    public String setupFromLegacy_SY(String arg) {
        if("Ov".equals(arg))    return setupFromLegacy_SY结束否(arg);
        else if("ca".equals(arg))    return setupFromLegacy_SY取消(arg);
        else if("Is".equals(arg))    return setupFromLegacy_SY启动("Isp");
        else if("cP".equals(arg))    return setupFromLegacy_SY启动("Company");
        else if("pS".equals(arg))    return setupFromLegacy_SY启动("Person");
        else if("eQ".equals(arg))    return setupFromLegacy_SY启动("Eqp");
        else if("Pu".equals(arg))    return setupFromLegacy_SY启动("PipingUnit");
        else return null;
    }
    public String setupFromLegacy_SY结束否(String arg) {
        Boolean ok = searchService.isCompleteMassIndex(null);
        Duration duration = Duration.between(searchService.getBeginTime(), Instant.now());
        MyMassIndexingLoggingMonitor monitor=searchService.getMassIndexingMonitor();
        Assert.notNull(monitor, "沒开启");
        if(ok)   duration= Duration.between(searchService.getBeginTime(), monitor.getCompleteTime());
        String desc="同步索引"+(ok? "任务结束,用时" : "进行中,持续") +duration.toString()+",比例"+monitor.getFinishPercent()*100
                            + "实体:" + searchService.getMassindexEntities();
        log.info(desc);
        return desc;
    }
    public String setupFromLegacy_SY取消(String arg) {
        Boolean ok = searchService.cancelMassIndex(null);
        Duration duration = Duration.between(searchService.getBeginTime(), Instant.now());
        MyMassIndexingLoggingMonitor monitor=searchService.getMassIndexingMonitor();
        Assert.notNull(monitor, "沒开启");
        String desc="取消同步"+(ok? "成功" : "失败")+",持续"+duration.toString()+",比例"+monitor.getFinishPercent()*100
                            + "实体:" + searchService.getMassindexEntities();
        log.info(desc);
        return desc;
    }
    public String setupFromLegacy_SY启动(String arg) {
        Boolean ok = searchService.startMassIndex(arg);
        String desc="启动同步ES索引"+(ok? "成功" : "失败")+",实体:"+arg;
        log.info(desc);
        return desc;
    }
    /**转换楼盘小区-》Village;
     * */
    public <T> Triple makeInf导楼盘小区vL(T inObj) {
        HouseMge parent= (HouseMge)inObj;
        if("删除".equals(parent.getBUILD_STATE()))    return new Triple("", null, null);
        QVillage qm = QVillage.village;
        BooleanBuilder builder = new BooleanBuilder();
        builder.and(qm.oldId.eq(parent.getId()));       //可重做模式，不用清空旧的Village表。
        Village village=villageRepository.findOne(builder).orElse(null);
        if(null==village)   village=new Village();
        village.setName(parent.getBUILD_NAME());
        village.setType(parent.getINST_BUILD_TYPE());
        village.setOldId(parent.getId());
        village.setAddress(parent.getBUILD_ADDR());
        Adminunit adminunit=adminunitRepository.findTopByAreacode(parent.getAREA_COD());
        if(null!=adminunit) {
            BooleanBuilder builder2 = new BooleanBuilder();
            builder2.and(qm.ad.eq(adminunit));
            builder2.and(qm.name.eq(parent.getBUILD_NAME()));
            builder2.and(qm.oldId.ne(parent.getId()));      //不是当前这个
            Village village2=villageRepository.findOne(builder2).orElse(null);
            if(null!=village2){   //BUILD_NAME多个空格能分辨清楚；反而是其他标点符号全角半角无法区分造成重复。奇怪IDEA console查询可当成不同的.
                return new Triple("该Admin下重复楼盘name", null, null);
            }else
                village.setAd(adminunit);
        }
        villageRepository.save(village);
        log.info("楼盘:{} AdPr={}",parent.getBUILD_NAME(), null!=adminunit? adminunit.getName() : parent.getBUILD_ADDR());
        return new Triple("", null, null);
    }
    /*从对接表 提取科室
    * */
    public <T> Triple makeInf检验机构科室(T inObj) {
        HrDeptinfo parent= (HrDeptinfo)inObj;
        if(1!=parent.getStatus() || null==parent.getParentId())    return new Triple("", null, null);    //非科室的
        HrDeptinfo village=hrDeptinfoRepository.findById(parent.getParentId()).orElse(null);
        if(null==village)   throw new CommonGraphQLException("科室没部门id", parent.getParentId());
        if(null==village.getDivision())   throw new CommonGraphQLException("科室没部门uuid", parent.getParentId());
        Division division=divisionRepository.findById(village.getDivision()).orElse(null);
        Assert.hasText(parent.getDEPT_NAME(),"空名字");
        Office officeOld=division.getOffices().stream().filter(a->parent.getDEPT_NAME().equals(a.getName())).findFirst().orElse(null);
        if(null==officeOld){
            Office obj=Office.builder().name(parent.getDEPT_NAME()).dep(division).build();
            officeRepository.save(obj);
        }
        log.info("科室:{} of {}",parent.getDEPT_NAME(), division.getName());
        return new Triple("", null, null);
    }
    public Boolean makeSomeForVF(JsliceMang jobm,String arg) {
        if("jS".equals(arg)){
            Function<UntSecudept, Triple<?,?,?>>  sliceJob= parb ->{
                   return genrVlgDivisionVF分支机构jS(parb);
            };
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return searchAfterL(jobm, untSecudeptRepository, sliceJob, null, null, UntSecudept.class);
        }
        else if("jD".equals(arg)){
            Function<UntDept, Triple<?,?,?>>  sliceJob= parb ->{
                return genrVlgDivisionVF安全部门jD(parb);
            };
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return searchAfterL(jobm, untDeptRepository, sliceJob, null, null, UntDept.class);
        }
        else return false;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    //注意：先单位必须整好，才能更新部门分支表
    public <T> Triple  genrVlgDivisionVF分支机构jS(T inObj) {
        UntSecudept iden= (UntSecudept)inObj;
        if(!StringUtils.hasText(iden.getName()) || null==iden.getUNT_ID())   return new Triple("非法", null, null);
        Unit unit= getUnitfromOldUnitId(false,iden.getUNT_ID());
        if(null==unit)   return new Triple("", null, null);
        Adminunit adminunit=adminunitRepository.findTopByAreacode(iden.getSECUDEPT_AREA_COD());
        Division division= divisionRepository.findByUnitAndName(unit, iden.getName());
        if(null==division)  division=new Division();
        division= division.toBuilder().name(iden.getName()).unit(unit).area(iden.getSECUDEPT_AREA_COD()).address(iden.getSECUDEPT_ADDR())
                .linkMen(iden.getLKMEN()).phone(iden.getMOBILE()).tel(iden.getPHONE()).ad(adminunit)
                .branch(true).frot("闽yS").build();
        divisionRepository.save(division);
        return new Triple("", null, null);
    }
    public <T> Triple  genrVlgDivisionVF安全部门jD(T inObj) {
        UntDept iden= (UntDept)inObj;
        if(!StringUtils.hasText(iden.getName()) || null==iden.getUntId())   return new Triple("非法", null, null);
        Unit unit= getUnitfromOldUnitId(false,iden.getUntId());
        if(null==unit)  return new Triple("", null, null);
        Adminunit adminunit=adminunitRepository.findTopByAreacode(iden.getDEPT_AREA_COD());
        Division division= divisionRepository.findByUnitAndName(unit, iden.getName());
        if(null==division)  division=new Division();
        division= division.toBuilder().name(iden.getName()).unit(unit).area(iden.getDEPT_AREA_COD()).address(iden.getDEPT_ADDR())
                .linkMen(iden.getLKMEN()).phone(iden.getMOBILE()).tel(iden.getPHONE()).ad(adminunit)
                .branch(false).frot("闽yD").build();
        divisionRepository.save(division);
        return new Triple("", null, null);
    }
    public Boolean makeSomeForDm(JsliceMang jobm,String arg) {
        Function<StoreSync, Triple<?,?,?>>  sliceJob= parb ->{
            if(".1".equals(arg) || ".2".equals(arg) || ".3".equals(arg) || ".4".equals(arg) || ".5".equals(arg) )
                return syncEqp单设备Dm(parb, arg);
            else return null;
        };
        return jobm.runChannelSlice(storeSyncRepository, sliceJob, null, null,StoreSync.class);
    }
    public Boolean makeSomeForPi(JsliceMang jobm,String arg) {
        Function<PipeLineUnit, Triple<?,?,?>>  sliceJob= parb ->{
            if(StringUtils.hasText(arg) && arg.startsWith("EN."))
                return syncPipeDevice(parb, arg);
            else return null;
        };
        return jobm.runChannelSlice(pipeLineUnitRepository, sliceJob, null, null,PipeLineUnit.class);
    }
    public <T> Triple<StoreSyncRepository,Object,Object>  syncEqp单设备Dm(T inObj, String arg) {
        StoreSync parb= (StoreSync)inObj;
        try {
            String result=checkDevice缺少eqp(parb, arg);
            return new Triple(result, null, null);
        } catch (Exception e) {
            e.printStackTrace();
//            throw new CommonGraphQLException("inf", e.getMessage());
        }
        return new Triple("", null, null);
    }
    //预先需处理 update public.storesync set fail=null where fail is not null :太慢了
    public String checkDevice缺少eqp(StoreSync ssi, String threadKey) {
        String retRes="";
        log.info("{}通道，取设备:cod{}",threadKey,ssi.getCod());
        User user=checkAuth();
        if(null==user)  return null;
        String cod=ssi.getCod(), oid=ssi.getOid();
        Unit ispu= user.getUnit();
        assertNotNull(ispu, () -> "ispu空");
        assertTrue(StringUtils.hasText(cod), () -> "cod=nul");
        //eqp = eQPRepository.findByOidAndSvu(oid, svu);  //找出多个设备=抛出异常!分片作业停止了;
        Eqp eqp= eQPRepository.findByCodAndIspu(ssi.getCod(),ispu);
        if(null==eqp)  return "未生成eqp";
        ssi.setDev(eqp);
        if(StringUtils.hasText(oid)){
           if(!oid.equals(eqp.getOid()))  return "oid变不同";
        }
        else if(StringUtils.hasText(eqp.getOid()))  return "oid变不同";
        return retRes;
    }
}
